# 路由&请求参数绑定
# RESTful
RESTful是一个资源定位及资源操作的风格。使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作,分别对应 添加、 删除、修改、查询
需求:RESTful方式实现商品信息查询,返回json数据
从URL上获取参数:根据id查询商品,使用RESTful风格开发的接口地址是:http://127.0.0.1/item/1
注解
@RequestMapping("item/{id}")声明请求的URL,{xxx}为占位符,请求的URL是“item /1”使用
@PathVariable() Integer id获取URL上的数据@RequestMapping("item/{id}") public @ResponseBody Item queryItemById(@PathVariable Integer id) { Item item = this.itemService.queryItemById(id); return item; }- 如果
@RequestMapping中表示为"item/{id}",id和形参名称一致,@PathVariable不用指定名称。如果不一致,例如"item/{ItemId}"则需要指定名称@PathVariable("itemId")
- 如果
注意:
- @PathVariable是获取url上数据的。@RequestParam获取请求参数的(包括post表单提交)
- 如果加上@ResponseBody注解,就不会走视图解析器,不会返回页面,返回如json数据。如果不加,就走视图解析器,返回页面
- 注意:
- 表单只支持GET、POST请求,若要发送其他请求(一般也不会使用表单发送其他请求),表单本身设置为POST请求,并需要input中属性
name="_method" value="PUT" - 后端中需要配置过滤器
org.springframework.web.filter.HiddenHttpMethodFilter - Ajax 支持其他类型的请求
- 表单只支持GET、POST请求,若要发送其他请求(一般也不会使用表单发送其他请求),表单本身设置为POST请求,并需要input中属性
# 路由
# @Controller
# @RestController 🔥
是@Controller和@ResponseBody的组合
使用 @ResponseBody 会自动设置如下代码:
设置 ContentType、MIME Type。如使用 UTF-8 解决乱码
response.setContentType("application/json;charset=utf-8");// 返回对象为 Object 时 response.setContentType("text/plain;charset=utf-8");// 返回对象为 String 时序列化、调用 response 的输出方法
response.getWriter().write("hello 测试2");// 若输出对象,则省掉手动序列号操作
# @RequestMapping
封装了 Servlet 的 url-partten
作用:用于建立请求 URL 和处理请求方法之间的对应关系。所有请求方法都会经过此路由
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
出现位置:
类上:请求URL的第一级访问目录,模块化管理。此处不写的话,就相当于应用的根目录。写的话需要以
/开头,如/user方法上:请求 URL 的第二级访问目录。 如
/add。但是RESTful风格的可以使用@**Mapping替代当使用分级配置时,前端页面的请求路径中,要么写绝对路径即带
/和项目名,要么写相对路径不带/(表示相对应用的根目录),否则404
属性:
value:用于指定请求的 URL数组。它和 path 属性的作用是一样的。大小写敏感。method:用于指定请求的方式,值为RequestMethod枚举类数组。可以有多个请求方式映射一个方法,但是下面的简写注解不可以在同一个方法中使用多个,否则只有最先写的起作用!
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。如:params = {"accountName"},表示请求参数必须有 accountNameparams = {"moeny!100"},表示请求参数中 money 不能是 100
headers:用于限定发送的请求中必须包含某请求头
# @GetMapping
用在方法上,替代方法的@RequestMapping
# @PostMapping
用在方法上,替代方法的@RequestMapping
# @PutMapping
用在方法上,替代方法的@RequestMapping
# @DeleteMapping
用在方法上,替代方法的@RequestMapping
# @PatchMapping
用在方法上,替代方法的@RequestMapping。部分更新
# Url 参数
# 经典— @RequestParam
用于映射请求的参数名和处理器形参名, 当二者一致时(区分大小写)可省略该注解!
注意:
- 🔥参数类型推荐使用包装数据类型,因为基础数据类型不可为null;
- 🔥Boolean 布尔类型的参数,请求的参数值可以为 true 或 false,1 或 0,接收后自动映射为布尔类型
属性:
value或name:请求参数中的名称。若参数名称和形参一致,可以不用指定 value 或 namerequired:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错defaultValue:请求参数默认值
Postman 请求如下(直接在 Params 中写 Query Params即可):
GET /v1/test/url2?name=conanan&flag=1 HTTP/1.1
Host: http://127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
和 Content-Type: application/x-www-form-urlencoded 没有关系,默认就是该类型,只需注意?后的键值对即可
Java 后台可以如下方式接收(只需切换下 url 即可)
@GetMapping("/url1")
public String url1(String name, Boolean flag){
return String.format("url1: %s, %s", name, flag);
}
// 可省略 @RequestParam
@GetMapping("/url2")
public String url2(@RequestParam String name, Boolean flag){
return String.format("url2: %s, %s", name, flag);
}
# RESTful — @PathVariable
用于绑定 url 中的占位符。url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 RESTful 风格 URL 的一个重要标志。
注意:
- 🔥参数类型推荐使用包装数据类型,因为基础数据类型不可为null;但这里一般不会出现
/rest//1情况 - 🔥Boolean 布尔类型的参数,请求的参数值可以为 true 或 false,1 或 0,接收后自动映射为布尔类型
属性:
value或name:用于指定 url 中占位符名称。若占位符名称和形参一致,可以不用指定value。required:是否必须提供占位符。
Postman 请求如下(直接在 Params 中写 Query Params即可):
GET /v1/test/rest/conanan/1 HTTP/1.1
Host: http://127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
和 Content-Type: application/x-www-form-urlencoded 没有关系,默认就是该类型
Java 后台可以如下方式接收(只需切换下 url 即可)
@GetMapping("/rest/{name}/{flag}")
public String rest1(@PathVariable String name,
@PathVariable Boolean flag){
return String.format("url2: %s, %s", name, flag);
}
# Body 参数
注意
GET 请求没有 Body 参数
# GET 表单—同 @RequestParam
表单参数绑定:
- 表单提交的数据都是k=v格式的
username=haha&password=123,key就是表单中name的值。GET 请求将数据放入 Url 的?后 - SpringMVC 的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 其他和经典
@RequestParam一致!!!
注意
需要注意的是,Postman 中 Body 选择 x-www-form-urlencoded,且方法为 GET 的不是真正的 GET 表单提交,它不会把键值对放入?后!!!所以还是推荐使用@RequestParam时采用 Query Params 即可
# POST 表单—直接 POJO 接收
表单参数绑定:
表单提交的数据都是k=v格式的
username=haha&password=123,key就是表单中name的值。POST 请求将数据放入请求体中SpringMVC 的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型
给 List 集合中的元素赋值,使用下标
给 Map 集合中的元素赋值,使用键值对
Postman 请求如下(在 Body 中 x-www-form-urlencoded 写参数即可):
POST /v1/test/body1 HTTP/1.1
Host: http://127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
name=conanan&
sex=1&
address.provinceName=shannxi&
address.cityName=xian&
petList[0].nikeName=nikeName1&
petList[0].sex=1&
petList[1].nikeName=nikeName2&
petList[1].sex=0&
petMap['one'].nikeName=nikeName3&
petMap['one'].sex=1&
petMap['two'].nikeName=nikeName4&
petMap['two'].sex=0
若参数重复,会出现其他问题,如最后一个 petMap['one'].sex=0,不会替换!
Content-Type: application/x-www-form-urlencoded,看其中 Body 请求体为键值对数据
Java 后台可以如下方式接收
@Data
public class Student {
private String name;
private Boolean sex;
private Address address;
private List<Pet> petList;
private Map<String, Pet> petMap;
}
@Data
public class Address {
private String provinceName;
private String cityName;
}
@Data
public class Pet {
private String nikeName;
private Boolean sex;
}
@PostMapping("/body1")
public String body1(Student student){
return String.format("body1: %s, %s, %s, %s, %s, %s",
student.getName(),
student.getSex(),
student.getAddress().getProvinceName(),
student.getAddress().getCityName(),
student.getPetList(),
student.getPetMap());
若有多个对象,则还是根据属性名称来接收参数!!!
@PostMapping("/body1")
public String body1(Student student, Teacher teacher){
return "";
}
若 Student 和 Teacher 有同名属性,则绑定后该同名属性的值相同!
# JSON—@RequestBody
作用:用于获取请求体内容(GET也有),常用于JSON数据封装。不使用则得到是key=value&key=value结构的数据。
属性:
required是否必须有请求体,默认为 true,即使是 GET 请求也必须添加请求体!
<form action="springmvc/useRequestBody" method="post">
用户名称:<input type="text" name="username" ><br/>
用户密码:<input type="password" name="password" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value=" 保存 "> </form>
@RequestMapping("/useRequestBody")
public String useRequestBody(@RequestBody(required=false) String body){
System.out.println(body);
return "success";
}
# 其他接收参数方式
# @DateTimeFormat
直接在JavaBean属性上添加注释即可(在get或set方法上添加,命名规范的话字段上添加也行。由于一般自动生成,所以都行)
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm")
private Date creationTime;
# 默认类型转换器
如 SpringMVC 会将a,b,c转为String[] 类型,但是不能专为List<String>,这就是它默认的一个类型转换器
# 自定义类型转换器
除了类型转换器,SpringMVC还提供了注解@DateTimeFormate来转换日期格式。查看5常用注解这一章。
SpringMVC还可以实现一些数据类型自动转换。内置转换器全都在org.springframework.core.convert.support包下。如String转Integer等等
如遇特殊类型转换要求,比如日期数据有很多种格式,SpringMVC没办法把带-字符串转换成日期类型,需要我们自己编写自定义类型转换器。步骤如下:
定义一个类,实现 Spring提供的
Converter接口,该接口有两个泛型。//Converter<S, T>: S:source,需要转换的源的类型;T:target,需要转换的目标类型 @Componet public class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String source) { DateFormat format = null; try { if (StringUtils.isEmpty(source)) { throw new NullPointerException("请输入要转换的日期"); } format = new SimpleDateFormat("yyyy-MM-dd"); Date date = format.parse(source); return date; } catch (Exception e) { throw new RuntimeException("输入日期有误"); } } }在 spring配置文件中配置类型转换器。JavaConfig暂时不会怎么配置
spring 配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。
<!-- 配置类型转换器工厂 --> <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <!--FormattingConversionServiceFactoryBean可以让SpringMVC支持和@DateTimeFormat等Spring内部自定义的转换器,建议--> <!-- 给工厂注入一个新的类型转换器 --> <property name="converters"> <array> <!-- 配置自定义类型转换器 --> <ref bean="stringToDateConverter"/> </array> </property> </bean> <!-- 配置spring开启注解mvc的支持,替代处理器映射器和处理器适配器配置,并配置类型转换器 --> <mvc:annotation-driven conversion-service="conversionService" />
# Map 接收参数
在 Controller 层不知道前端都会传递哪些参数给到后端,为了扩展方便(但是,不能传到 Service 层还是 Map 吧!只能在 Controller 接收时方便点)。许多框架(如 CXF 不允许接收 Map 参数),毕竟不能把 Java 写成 JS 是吧。
- SpringMVC处理请求用Map类型接收参数时,如果参数无注解,则会传入BindingAwareModelMap类型,等价于Model、ModelMap参数。所以无法接受到传入的参数
- 参数添加 @RequestParam 注解时,会将参数包装称 LinkedHashMap 对象,参数的key为Map的key,参数值为Map的key,支持Get、Post方法(应该支持Put、Delete,没有测,俩方法与Post类似)。可以封装 PageBean 参数!
- 添加 @RequestBody 注解时,接收 Json 类型数据,也会包装成 LinkedHashMap 对象,该注解不支持Get请求,Get请求没有请求体不能传Json。
# Servlet Api
控制器(处理器)形参中添加如下类型的参数,处理适配器会默认识别并进行赋值
HttpServletRequest:通过request对象获取请求信息HttpServletResponse:通过response处理响应信息HttpSession:通过session对象得到session中存放的对象
# @RequestHeader
一般不怎么用
- 作用:用于获取请求消息头。
- 属性:
value提供消息头名称。required是否必须有此消息头
# @CookieValue
一般不怎么用
- 作用:用于把指定 cookie 名称的值传入控制器方法参数。
- 属性:
value指定 cookie 的名称。required是否必须有此 cookie
# @SessionAttribute
作用:用于多次执行控制器方法间的参数共享。
属性:
value:用于指定存入的属性名称type:用于指定存入的数据类型。
@Controller("sessionAttributeController") @RequestMapping("/springmvc") @SessionAttributes(value= {"username","password","age"},types= {String.class,Integer.class})//存入到session域 public class SessionAttributeController { @RequestMapping("/testPut") public String testPut(Model model){ model.addAttribute("username", "泰斯特"); model.addAttribute("password","123456"); model.addAttribute("age", 31); //跳转之前将数据保存到 username、password 和 age 中,因为注解@SessionAttribute 中有这几个参数 return "success"; } @RequestMapping("/testGet") public String testGet(ModelMap model){ System.out.println(model.get("username")+";"+model.get("password")+";"+model.get("age")); return "success"; } @RequestMapping("/testClean") public String complete(SessionStatus sessionStatus){ sessionStatus.setComplete(); return "success"; } }
# @ModelAttribute
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
- 出现在方法上,表示当前方法会在控制器的方法执行之前先执行。它可以修饰没有返回值和有具体返回值的方法
- 出现在参数上,获取指定的数据给参数赋值
属性:
value用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。但是这种问题有其他办法解决。
基于POJO 属性的基本使用
<a href="springmvc/testModelAttribute?username=test">测试 modelattribute</a>@ModelAttribute public void showModel(User user) { System.out.println("执行了 showModel 方法"+user.getUsername()); } @RequestMapping("/testModelAttribute") public String testModelAttribute(User user) { System.out.println("执行了控制器的方法"+user.getUsername()); return "success"; } //执行了 showModel 方法 //执行了控制器的方法基于 Map 的应用场景示例 1:ModelAttribute 修饰方法带返回值
<!--需求: 修改用户信息,要求用户的密码不能修改 --> <form action="springmvc/updateUser" method="post"> 用户名称:<input type="text" name="username" ><br/> 用户年龄:<input type="text" name="age" ><br/> <input type="submit" value=" 保存 "> </form>// 模拟修改用户方法 @RequestMapping("/updateUser") public String testModelAttribute(User user) { System.out.println("控制器中处理请求的方法:修改用户:"+user); return "success"; } @ModelAttribute public User showModel(String username) { //模拟去数据库查询 User abc = findUserByName(username); System.out.println("执行了 showModel 方法"+abc); return abc; } // 模拟去数据库查询 private User findUserByName(String username) { User user = new User(); user.setUsername(username); user.setAge(19); user.setPassword("123456"); return user; } //输出会给未提交的age字段赋值19,其他的使用提交的数据基于 Map 的应用场景示例 2:ModelAttribute 修饰方法不带返回值
@RequestMapping("/updateUser") public String testModelAttribute(@ModelAttribute("abc")User user) { System.out.println("控制器中处理请求的方法:修改用户:"+user); return "success"; } @ModelAttribute public void showModel(String username,Map<String,User> map) { //模拟去数据库查询 User user = findUserByName(username); System.out.println("执行了 showModel 方法"+user); map.put("abc",user); } // 模拟去数据库查询 private User findUserByName(String username) { User user = new User(); user.setUsername(username); user.setAge(19); user.setPassword("123456"); return user; } //输出会给未提交的age字段赋值19,其他的使用提交的数据
# Multipart 文件
# 文件上传的回顾
- form表单的
enctype取值必须是multipart/form-data(默认值是application/x-www-form-urlencoded)。enctype代表表单请求正文的类型 method属性取值必须是Post- 提供一个文件选择域
<input type="file" />
<form action="user/fileupload" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/><br/>
<input type="submit" value="上传文件"/>
</form>
@RequestMapping(value="/fileupload")
public String fileupload(HttpServletRequest request) throws Exception {
// 先获取到要上传的文件目录
String path = request.getSession().getServletContext().getRealPath("/uploads");
// 创建File对象,一会向该路径下上传文件
File file = new File(path);
// 判断路径是否存在,如果不存在,创建该路径
if(!file.exists()) {
file.mkdirs();
}
// 创建磁盘文件项工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileUpload = new ServletFileUpload(factory);
// 解析request对象
List<FileItem> list = fileUpload.parseRequest(request);
// 遍历
for (FileItem fileItem : list) {
// 判断文件项是普通字段,还是上传的文件
if(fileItem.isFormField()) {
}else {
// 上传文件项
// 获取到上传文件的名称
String filename = fileItem.getName();
// 上传文件
fileItem.write(new File(file, filename));
// 删除临时文件
fileItem.delete();
}
}
return "success";
}
# SpringMVC传统方式的文件上传
pom.xml中添加依赖(可不加)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency
传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。 并且上传完成之后,浏览器可能跳转。
@RequestMapping(value="/fileupload2")
public String fileupload2(HttpServletRequest request,MultipartFile upload) throws Exception {
System.out.println("SpringMVC方式的文件上传...");
// 先获取到要上传的文件目录
String path = request.getSession().getServletContext().getRealPath("/uploads");
// 创建File对象,一会向该路径下上传文件
File file = new File(path);
// 判断路径是否存在,如果不存在,创建该路径
if(!file.exists()) {
file.mkdirs();
}
// 获取到上传文件的名称
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 把文件的名称唯一化
filename = uuid+"_"+filename;
// 上传文件
upload.transferTo(new File(file,filename));
return "success";
}
SpringBoot配置
JavaConfig配置MultipartResolver接口的实现类
CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求StandardServletMultipartResolver:依赖于Servlet3.0对multipart请求支持(始于Spring3.1)选择这个,它使用Servlet所提供的功能支持,不依赖其他项目。它没有构造器参数和属性
@Bean public MultipartResolver multipartResolver() throws IOException { return new StandardServletMultipartResolver(); }如果配置DispatcherServlet的Servlet初始化类继承了AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer的话,通过重载customize Registration()方法(它会得到Dynamic参数)来配置multipart的具体细节
//class Config extends AbstractAnnotationConfigDispatcherServletInitializer @Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/tmp/file/uploads",2097152,4194304,0)); //location,maxFileSize,maxRequestSize,fileSizeThreshold(为0则上传文件写到磁盘) }
在
spring-config.xml配置文件解析器<!-- 配置文件上传解析器,id是固定的!!!--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件的最大尺寸为 5MB --> <property name="maxUploadSize"> <value>5242880</value> </property> </bean>
# SpringMVC跨服务器方式的文件上传
在实际开发中,我们会有很多处理不同功能的服务器(不是服务器集群),目的是让服务器各司其职,从而提高我们项目的运行效率。例如:
- 应用服务器:负责部署我们的应用
- 文件服务器:负责存储用户上传文件的服务器
- 数据库服务器:运行我们的数据库
- ……
步骤:
搭建图片服务器
- 根据文档配置tomcat9的服务器,现在是2个服务器
- 导入资料中day02_springmvc5_02image项目,作为图片服务器使用
实现SpringMVC跨服务器方式文件上传
导入依赖的jar包的坐标(sun公司提供的,下面导包时注意)
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.18.1</version> </dependency>控制器
@RequestMapping(value="/fileupload3") public String fileupload3(MultipartFile upload) throws Exception { System.out.println("SpringMVC跨服务器方式的文件上传..."); // 定义图片服务器的请求路径 String path = "http://localhost:9090/day02_springmvc5_02image/uploads/";//创建好该文件夹 // 获取到上传文件的名称 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); // 把文件的名称唯一化 filename = uuid+"_"+filename; // 向图片服务器上传文件 // 创建客户端对象 Client client = Client.create(); // 连接图片服务器 WebResource webResource = client.resource(path+filename); // 上传文件 webResource.put(upload.getBytes()); return "success"; }配置文件解析器,同上